<template>
  <div class="ns-page flex-col">
    <div class="ns-page-header">
      <h1 v-if="mode === 'platform'">交易平台日志</h1>
      <h1 v-else>{{ moduleName }}模组日志</h1>
    </div>
    <div class="ns-page-action">
      <el-form inline label-width="70px">
        <el-form-item label="打印级别">
          <el-button-group class="mr-20">
            <el-button 
              :type="logLevel === 'ERROR' ? 'primary' : ''" 
              @click="logLevel = 'ERROR'"
              :title="'只显示ERROR'">
              ERROR
            </el-button>
            <el-button 
              :type="logLevel === 'WARN' ? 'primary' : ''" 
              @click="logLevel = 'WARN'"
              :title="'显示ERROR和WARN'">
              WARN
            </el-button>
            <el-button 
              :type="logLevel === 'INFO' ? 'primary' : ''" 
              @click="logLevel = 'INFO'"
              :title="'显示ERROR、WARN、INFO'">
              INFO
            </el-button>
            <el-button 
              :type="logLevel === 'DEBUG' ? 'primary' : ''" 
              @click="logLevel = 'DEBUG'"
              :title="'显示ERROR、WARN、INFO、DEBUG'">
              DEBUG
            </el-button>
            <el-button 
              :type="logLevel === 'TRACE' ? 'primary' : ''" 
              @click="logLevel = 'TRACE'"
              :title="'显示所有级别'">
              TRACE
            </el-button>
          </el-button-group>
        </el-form-item>
        <el-form-item label="刷新频率">
          <el-button-group class="mr-20">
            <el-button
              :type="updateInterval === 1000 ? 'primary' : ''"
              @click="updateInterval = 1000"
              >1秒</el-button
            >
            <el-button
              :type="updateInterval === 2000 ? 'primary' : ''"
              @click="updateInterval = 2000"
              >2秒</el-button
            >
            <el-button
              :type="updateInterval === 5000 ? 'primary' : ''"
              @click="updateInterval = 5000"
              >5秒</el-button
            >
          </el-button-group>
        </el-form-item>
        <el-form-item label="显示行数">
          <el-button-group class="mr-20">
            <el-button
              :type="numOfLinesInView === 100 ? 'primary' : ''"
              @click="numOfLinesInView = 100"
              >100行</el-button
            >
            <el-button
              :type="numOfLinesInView === 200 ? 'primary' : ''"
              @click="numOfLinesInView = 200"
              >200行</el-button
            >
            <el-button
              :type="numOfLinesInView === 500 ? 'primary' : ''"
              @click="numOfLinesInView = 500"
              >500行</el-button
            >
            <el-button
              :type="numOfLinesInView === 5000 ? 'primary' : ''"
              @click="numOfLinesInView = 5000"
              >5000行</el-button
            >
          </el-button-group>
        </el-form-item>
        <el-form-item label="自动滚动">
          <el-switch v-model="autoScroll"></el-switch>
        </el-form-item>
        <el-form-item label="停止日志输出">
          <el-switch v-model="pauseLogOutput" @change="handlePauseChange"></el-switch>
        </el-form-item>
      </el-form>
    </div>

    <div ref="logView" v-if="filteredLogContent.length" class="ns-page-body log-selectable">
      <div v-for="(line, i) in filteredLogContent" :key="i">{{ line }}</div>
    </div>
    <div
      v-else
      class="ns-page-body-placeholder flex align-center"
      v-loading="loading"
      element-loading-background="rgba(0, 0, 0, 0.3)"
    >
      没有日志数据
    </div>
    <div class="ns-page-footer" style="margin-top: 10px; font-size: 12px; color: #909399;" v-if="moduleName || mode === 'platform'">
      <span v-if="mode === 'platform'">日志文件路径：{{ absoluteLogPath }}/Northstar_*.log</span>
      <span v-else-if="moduleName">日志文件路径：{{ absoluteLogPath }}/{{ moduleName }}/module.log</span>
    </div>
  </div>
</template>

<script>
import logApi from '@/api/logApi'

const LOG_UPDATE_INTERVAL = 'logUpdateInterval'
const LOG_NUM_OF_LINES_IN_VIEW = 'numOfLinesInView'
export default {
  data() {
    return {
      mode: 'platform',
      moduleName: '',
      logLevel: '',
      updateInterval: 1000,
      numOfLinesInView: 100,
      positionOffset: 0,
      logContent: [],
      timer: '',
      autoScroll: true,
      lastInit: 0,
      loading: true,
      logPath: 'logs',
      absoluteLogPath: 'logs',
      isLoadingHistory: false,
      pauseLogOutput: false
    }
  },
  computed: {
    filteredLogContent() {
      if (!this.logLevel || this.logLevel === '') {
        return this.logContent.slice(-this.numOfLinesInView)
      }
      const levelOrder = { 'ERROR': 0, 'WARN': 1, 'INFO': 2, 'DEBUG': 3, 'TRACE': 4 }
      const selectedLevel = levelOrder[this.logLevel]
      if (selectedLevel === undefined) {
        return this.logContent.slice(-this.numOfLinesInView)
      }
      const filtered = this.logContent.filter(line => {
        if (!line || typeof line !== 'string' || line.trim() === '') {
          return false
        }
        const levelMatch1 = line.match(/\s-\s(ERROR|WARN|INFO|DEBUG|TRACE)\s-/)
        const levelMatch2 = line.match(/\s+(ERROR|WARN|INFO|DEBUG|TRACE)\s+/)
        const levelMatch = levelMatch1 || levelMatch2
        if (!levelMatch || !levelMatch[1]) {
          return false
        }
        const matchedLevel = levelMatch[1].toUpperCase()
        const lineLevel = levelOrder[matchedLevel]
        if (lineLevel === undefined) {
          return false
        }
        return lineLevel <= selectedLevel
      })
      return filtered.slice(-this.numOfLinesInView)
    }
  },
  watch: {
    updateInterval: function (val) {
      localStorage.setItem(LOG_UPDATE_INTERVAL, val)
    },
    numOfLinesInView: async function (val) {
      localStorage.setItem(LOG_NUM_OF_LINES_IN_VIEW, val)
      await this.checkAndLoadHistory()
    },
    logLevel: async function (val) {
      if (this.mode === 'platform') {
        logApi.setPlatformLogLevel(val)
      } else {
        logApi.setModuleLogLevel(this.moduleName, val)
      }
      await this.checkAndLoadHistory()
    },
    '$route'() {
      if (this.timer) {
        clearTimeout(this.timer)
        this.timer = null
      }
      this.logContent = []
      this.positionOffset = 0
      this.pageInit()
    }
  },
  mounted() {
    this.pageInit()
  },
  destroyed() {
    if (this.timer) {
      clearTimeout(this.timer)
      this.timer = null
    }
  },
  methods: {
    async pageInit() {
      const time = new Date().getTime()
      if (time - this.lastInit < 1000) {
        return
      }
      this.lastInit = time
      this.updateInterval = parseInt(localStorage.getItem(LOG_UPDATE_INTERVAL) || 1000)
      this.numOfLinesInView = parseInt(localStorage.getItem(LOG_NUM_OF_LINES_IN_VIEW) || 500)
      try {
        this.absoluteLogPath = await logApi.getLogPath()
      } catch (error) {
        console.error('获取日志路径失败:', error)
        this.absoluteLogPath = 'logs'
      }
      const moduleName = this.$route.params.module || this.$route.query.module
      if (moduleName) {
        this.mode = 'module'
        this.moduleName = moduleName
        this.logLevel = await logApi.getModuleLogLevel(moduleName)
        this.tailModuleLog(moduleName)
      } else {
        this.mode = 'platform'
        this.logLevel = await logApi.getPlatformLogLevel()
        this.tailPlatformLog()
      }
      this.$nextTick(() => {
        this.checkAndLoadHistory()
      })
    },
    tailPlatformLog() {
      if (this.pauseLogOutput) {
        return
      }
      logApi.tailPlatformLog(this.positionOffset, this.numOfLinesInView).then((result) => {
        if (this.pauseLogOutput) {
          return
        }
        this.tailingLog(result)
        this.timer = setTimeout(this.tailPlatformLog, this.updateInterval)
      })
    },
    tailModuleLog(moduleName) {
      if (this.pauseLogOutput) {
        return
      }
      logApi
        .tailModuleLog(moduleName, this.positionOffset, this.numOfLinesInView)
        .then((result) => {
          if (this.pauseLogOutput) {
            return
          }
          this.tailingLog(result)
          this.timer = setTimeout(() => {
            this.tailModuleLog(moduleName)
          }, this.updateInterval)
        })
    },
    handlePauseChange(paused) {
      this.pauseLogOutput = paused
      if (!paused) {
        if (this.timer) {
          clearTimeout(this.timer)
          this.timer = null
        }
        if (this.mode === 'platform') {
          this.tailPlatformLog()
        } else {
          this.tailModuleLog(this.moduleName)
        }
      } else {
        if (this.timer) {
          clearTimeout(this.timer)
          this.timer = null
        }
      }
    },
    tailingLog(logDescription) {
      this.positionOffset = logDescription.endPosition
      const newLines = logDescription.linesOfLog
      const existingSet = new Set(this.logContent)
      newLines.forEach(line => {
        if (!existingSet.has(line)) {
          this.logContent.push(line)
          existingSet.add(line)
        }
      })
      if (this.logContent.length > this.numOfLinesInView * 3) {
        this.logContent = this.logContent.slice(-this.numOfLinesInView * 2)
      }
      this.loading = false
      if (this.autoScroll) {
        this.$nextTick(() => {
          if (this.$refs.logView) {
            this.$refs.logView.scrollTop = this.$refs.logView.scrollHeight
          }
        })
      }
    },
    filterLogLines(lines) {
      if (!this.logLevel || this.logLevel === '') {
        return lines
      }
      const levelOrder = { 'ERROR': 0, 'WARN': 1, 'INFO': 2, 'DEBUG': 3, 'TRACE': 4 }
      const selectedLevel = levelOrder[this.logLevel]
      if (selectedLevel === undefined) {
        return lines
      }
      return lines.filter(line => {
        if (!line || typeof line !== 'string' || line.trim() === '') {
          return false
        }
        const levelMatch1 = line.match(/\s-\s(ERROR|WARN|INFO|DEBUG|TRACE)\s-/)
        const levelMatch2 = line.match(/\s+(ERROR|WARN|INFO|DEBUG|TRACE)\s+/)
        const levelMatch = levelMatch1 || levelMatch2
        if (!levelMatch || !levelMatch[1]) {
          return false
        }
        const matchedLevel = levelMatch[1].toUpperCase()
        const lineLevel = levelOrder[matchedLevel]
        if (lineLevel === undefined) {
          return false
        }
        return lineLevel <= selectedLevel
      })
    },
    async checkAndLoadHistory() {
      if (this.isLoadingHistory) {
        return
      }
      const filtered = this.filterLogLines(this.logContent)
      const filteredCount = filtered.length
      if (filteredCount >= this.numOfLinesInView) {
        return
      }
      this.isLoadingHistory = true
      const needMore = this.numOfLinesInView - filteredCount
      const maxReadSize = 10 * 1024 * 1024
      const startOffset = this.positionOffset > 0 ? Math.max(0, this.positionOffset - maxReadSize) : 0
      let currentOffset = startOffset
      const collectedLines = []
      const existingLinesSet = new Set(this.logContent)
      let lastStartPosition = -1
      while (collectedLines.length < needMore) {
        try {
          const result = this.mode === 'platform' 
            ? await logApi.tailPlatformLog(currentOffset, needMore * 3)
            : await logApi.tailModuleLog(this.moduleName, currentOffset, needMore * 3)
          if (!result || !result.linesOfLog || result.linesOfLog.length === 0) {
            break
          }
          if (result.startPosition === lastStartPosition) {
            break
          }
          lastStartPosition = result.startPosition
          const newLines = result.linesOfLog.filter(line => !existingLinesSet.has(line))
          const filteredLines = this.filterLogLines(newLines)
          collectedLines.unshift(...filteredLines)
          filteredLines.forEach(line => existingLinesSet.add(line))
          if (result.startPosition <= 0 || result.startPosition >= currentOffset) {
            break
          }
          currentOffset = result.startPosition
          if (collectedLines.length >= needMore) {
            break
          }
        } catch (error) {
          console.error('加载历史日志失败:', error)
          break
        }
      }
      if (collectedLines.length > 0) {
        const newContent = [...collectedLines.slice(-needMore), ...this.logContent]
        this.logContent = newContent.slice(-this.numOfLinesInView * 2)
      }
      this.isLoadingHistory = false
    }
  }
}
</script>

<style>
.ns-page-header h1 {
  margin-top: 0;
}
.ns-page-body {
  overflow: auto;
  overflow-wrap: anywhere;
}
.ns-page-body.log-selectable {
  user-select: text;
  -webkit-user-select: text;
  -moz-user-select: text;
  -ms-user-select: text;
}
.ns-page-body-placeholder {
  height: 100%;
}
</style>
